/*
 * Simulator of microcontrollers (t870c1.cc)
 *
 * Copyright (C) 2016 Drotos Daniel
 * 
 * To contact author send email to dr.dkdb@gmail.com
 *
 */

/* This file is part of microcontroller simulator: ucsim.

UCSIM is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

UCSIM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with UCSIM; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
/*@1@*/

#include <stdlib.h>
#include <ctype.h>

#include "utils.h"

// local
#include "t870c1cl.h"


cl_t870c1_psw_op::cl_t870c1_psw_op(class cl_memory_cell *acell,
				   class cl_t870c1 *auc):
  cl_memory_operator(acell)
{
  uc= auc;
}

t_mem
cl_t870c1_psw_op::write(t_mem val)
{
  val&= 0xfe;
  u8_t new_rbs= val & MRBS;
  if (new_rbs != uc->act_rbs)
    uc->change_bank(new_rbs);
  return val;
}


cl_t870c1::cl_t870c1(class cl_sim *asim):
  cl_t870c(asim)
{
}


int
cl_t870c1::init(void)
{
  cl_t870c::init();
  return 0;
}

void
cl_t870c1::part_init(void)
{
  rF&= ~MRBS;
  class cl_memory_operator *o= new cl_t870c1_psw_op(&cPSW, this);
  o->init();
  cPSW.append_operator(o);

  reg_cell_var(&cW0, &(rbanks[0].rwa.w), "W0", "W register in bank 0");
  reg_cell_var(&cA0, &(rbanks[0].rwa.a), "A0", "A register in bank 0");
  reg_cell_var(&cB0, &(rbanks[0].rbc.b), "B0", "B register in bank 0");
  reg_cell_var(&cC0, &(rbanks[0].rbc.c), "C0", "C register in bank 0");
  reg_cell_var(&cD0, &(rbanks[0].rde.d), "D0", "D register in bank 0");
  reg_cell_var(&cE0, &(rbanks[0].rde.e), "E0", "E register in bank 0");
  reg_cell_var(&cH0, &(rbanks[0].rhl.h), "H0", "H register in bank 0");
  reg_cell_var(&cL0, &(rbanks[0].rhl.l), "L0", "L register in bank 0");

  reg_cell_var(&cW1, &(rbanks[1].rwa.w), "W1", "W register in bank 1");
  reg_cell_var(&cA1, &(rbanks[1].rwa.a), "A1", "A register in bank 1");
  reg_cell_var(&cB1, &(rbanks[1].rbc.b), "B1", "B register in bank 1");
  reg_cell_var(&cC1, &(rbanks[1].rbc.c), "C1", "C register in bank 1");
  reg_cell_var(&cD1, &(rbanks[1].rde.d), "D1", "D register in bank 1");
  reg_cell_var(&cE1, &(rbanks[1].rde.e), "E1", "E register in bank 1");
  reg_cell_var(&cH1, &(rbanks[1].rhl.h), "H1", "H register in bank 1");
  reg_cell_var(&cL1, &(rbanks[1].rhl.l), "L1", "L register in bank 1");

  reg_cell_var(&cWA0, &(rbanks[0].wa), "WA0", "WA register in bank 0");
  reg_cell_var(&cBC0, &(rbanks[0].bc), "BC0", "BC register in bank 0");
  reg_cell_var(&cDE0, &(rbanks[0].de), "DE0", "DE register in bank 0");
  reg_cell_var(&cHL0, &(rbanks[0].hl), "HL0", "HL register in bank 0");
  reg_cell_var(&cIX0, &(rbanks[0].ix), "IX0", "IX register in bank 0");
  reg_cell_var(&cIY0, &(rbanks[0].iy), "IY0", "IY register in bank 0");

  reg_cell_var(&cWA1, &(rbanks[1].wa), "WA1", "WA register in bank 1");
  reg_cell_var(&cBC1, &(rbanks[1].bc), "BC1", "BC register in bank 1");
  reg_cell_var(&cDE1, &(rbanks[1].de), "DE1", "DE register in bank 1");
  reg_cell_var(&cHL1, &(rbanks[1].hl), "HL1", "HL register in bank 1");
  reg_cell_var(&cIX1, &(rbanks[1].ix), "IX1", "IX register in bank 1");
  reg_cell_var(&cIY1, &(rbanks[1].iy), "IY1", "IY register in bank 1");
}


void
cl_t870c1::mk_rbanks(void)
{
  rbanks= (struct rbank_870c_t *)malloc(sizeof(*rbanks));
  rbank= &rbanks[0];
  act_rbs= 0;
}

void
cl_t870c1::change_bank(u8_t new_rbs)
{
  rbank= &rbanks[(act_rbs= new_rbs)?1:0];
  decode_regs();
}


void
cl_t870c1::make_memories(void)
{
  class cl_address_space *as;

  rom= asc= as= new cl_address_space("code", 0, 0x10000, 8);
  as->init();
  address_spaces->add(as);

  asd= as= new cl_address_space("data", 0, 0x10000, 8);
  as->init();
  address_spaces->add(as);

  class cl_address_decoder *ad;
  class cl_memory_chip *chip;

  bootrom_chip= chip= new cl_chip8("bootrom_chip", 0x800, 8, 0xff);
  chip->init();
  memchips->add(chip);
  
  rom_chip= chip= new cl_chip8("rom_chip", 0x8000, 8, 0xff);
  chip->init();
  memchips->add(chip);
  
  ad= new cl_address_decoder(as= rom,
                             chip, 0x8000, 0xffff, 0);
  ad->init();
  as->decoders->add(ad);
  ad->activate(0);

  ad= new cl_address_decoder(as= asd,
                             chip, 0x8000, 0xffff, 0);
  ad->init();
  as->decoders->add(ad);
  ad->activate(0);

  ram_chip= chip= new cl_chip8("ram_chip", 0x800, 8);
  chip->init();
  memchips->add(chip);
  
  ad= new cl_address_decoder(as= asd,
                             chip, 0x40, 0x83f, 0);
  ad->init();
  as->decoders->add(ad);
  ad->activate(0);

  chip= new cl_chip8("sfr1_chip", 64, 8, 0);
  chip->init();
  memchips->add(chip);
  
  ad= new cl_address_decoder(as= asd,
                             chip, 0, 63, 0);
  ad->init();
  as->decoders->add(ad);
  ad->activate(0);

  chip= new cl_chip8("sfr2_chip", 256, 8, 0);
  chip->init();
  memchips->add(chip);
  
  ad= new cl_address_decoder(as= asd,
                             chip, 0xf00, 0xfff, 0);
  ad->init();
  as->decoders->add(ad);
  ad->activate(0);

  chip= new cl_chip8("sfr3_chip", 192, 8, 0);
  chip->init();
  memchips->add(chip);
  
  ad= new cl_address_decoder(as= asd,
                             chip, 0xe40, 0xeff, 0);
  ad->init();
  as->decoders->add(ad);
  ad->activate(0);
}


void
cl_t870c1::make_cpu_hw(void)
{
  add_hw(cpu= new cl_t870c1_cpu(this));
  cpu->init();
}


void
cl_t870c1::reset(void)
{
  cl_t870c::reset();
  cF.W(rF&~MRBS);
}


void
cl_t870c1::print_regs(class cl_console_base *con)
{
  con->dd_color("answer");
  con->dd_printf("JZCHSVB-  Flags= 0x%02x  ", rF);
  con->dd_printf("A= 0x%02x %3d %c\n",
		 rA, rA, isprint(rA)?rA:'.');
  con->dd_printf("%s\n", cbin(rF,8).c_str());
  if ((rF & MRBS) == 0)
    con->dd_color("answer");
  else
    con->dd_color("gray_answer");
  con->dd_printf("WA0=0x%04x [WA]=%02x  ", rWA0, asd->read(rWA0));
  con->dd_printf("BC0=0x%04x [BC]=%02x  ", rBC0, asd->read(rBC0));
  con->dd_printf("DE0=0x%04x [DE]=%02x\n", rDE0, asd->read(rDE0));
  con->dd_printf("HL0=0x%04x [HL]=%02x  ", rHL0, asd->read(rHL0));
  con->dd_printf("IX0=0x%04x [IX]=%02x  ", rIX0, asd->read(rIX0));
  con->dd_printf("IY0=0x%04x [IY]=%02x\n", rIY0, asd->read(rIY0));
  if ((rF & MRBS) != 0)
    con->dd_color("answer");
  else
    con->dd_color("gray_answer");
  con->dd_printf("WA1=0x%04x [WA]=%02x  ", rWA1, asd->read(rWA1));
  con->dd_printf("BC1=0x%04x [BC]=%02x  ", rBC1, asd->read(rBC1));
  con->dd_printf("DE1=0x%04x [DE]=%02x\n", rDE1, asd->read(rDE1));
  con->dd_printf("HL1=0x%04x [HL]=%02x  ", rHL1, asd->read(rHL1));
  con->dd_printf("IX1=0x%04x [IX]=%02x  ", rIX1, asd->read(rIX1));
  con->dd_printf("IY1=0x%04x [IY]=%02x\n", rIY1, asd->read(rIY1));

  con->dd_color("answer");
  con->dd_printf("SP =0x%04x [SP]=%02x  ", rSP, asd->read(rSP));
  con->dd_printf("\n");
  print_disass(PC, con);
}


/**************************************************************************/

cl_t870c1_cpu::cl_t870c1_cpu(class cl_uc *auc):
  cl_hw(auc, HW_DUMMY, 0, "cpu")
{
  uc= (class cl_t870c1 *)auc;
}

int
cl_t870c1_cpu::init(void)
{
  class cl_var *v;
  cl_hw::init();
  psw= register_cell(uc->asd, 0x3f);
  uc->vars->add(v= new cl_var(chars("sp_limit"), cfg,
			      t870c_sp_limit,
			      cfg_help(t870c_sp_limit)));
  v->init();

  uc->vars->add(v= new cl_var(chars("bootmode"), cfg,
			      t870c1_bootmode,
			      cfg_help(t870c1_bootmode)));
  v->init();

return 0;
}

void
cl_t870c1_cpu::write(class cl_memory_cell *cell, t_mem *val)
{
  if (conf(cell, val))
    return;
  if (cell == psw)
    {
      *val= uc->rPSW;
    }
}

t_mem
cl_t870c1_cpu::read(class cl_memory_cell *cell)
{
  t_mem v= cell->get();
  if (conf(cell, NULL))
    return v;
  if (cell == psw)
    {
      v= uc->rPSW;
    }
  return v;
}

t_mem
cl_t870c1_cpu::conf_op(cl_memory_cell *cell, t_addr addr, t_mem *val)
{
  switch ((enum t870c_cpu_cfg)addr)
    {
    case t870c1_sp_limit:
      if (val)
	uc->sp_limit= *val & 0xffff;
      return uc->sp_limit;
      break;
    case t870c1_bootmode:
      if (val)
	{
	  *val= (*val)?1:0;
	  // TODO: remap memories
	}
      break;
    default:
      if (val)
	cell->set(*val);
    }
  return cell->get();
}

const char *
cl_t870c1_cpu::cfg_help(t_addr addr)
{
  switch (addr)
    {
    case t870c1_sp_limit:
      return "Stack overflows when SP reaches this limit (uint, RW)";
    case t870c1_bootmode:
      return "If true, CPU works in boot mode (bool, RW)";
    }
  return "Not used";
}


/* End of tlcs.src/t870c1.cc */
